﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Compiler
{
    public class Grammar
    {
        private HashSet<Terminal> Terminals;
        private HashSet<Nonterminal> NonTerminals;

        public readonly Nonterminal Start;
        public Dictionary<Nonterminal, List<Production>> Productions;

        public IEnumerable<Terminal> getTerminals()
        {
            return Terminals;
        }

        public IEnumerable<Nonterminal> getNonterminals()
        {
            return NonTerminals;
        }

        public Grammar(Nonterminal Start, params Production[] rules)
        {
            Productions = new Dictionary<Nonterminal, List<Production>>();
            NonTerminals = new HashSet<Nonterminal>();
            Terminals = new HashSet<Terminal>();

            this.Start = Start;

            PushRules(rules);

            //todo: hier "echte" startregel erzwingen. d.h. für das start-nonterminal darf es nur eine regel geben,
            //diese regel darf nur ein symbol im tail haben. ist das eine zugroße einschränkung? nee glaube nicht.
            if (!NonTerminals.Contains(Start))
                throw new Exception("Startsymbol not in any Production");
        }

        private void PushRule(Production rule)
        {
            List<Production> prods;
            if (!Productions.TryGetValue(rule.Head, out prods))
            {
                prods = new List<Production>();
                Productions.Add(rule.Head, prods);
            }
            prods.Add(rule);

            NonTerminals.Add(rule.Head);
            NonTerminals.UnionWith(rule.GetNonTerminalsInTail());

            Terminals.UnionWith(rule.GetTerminalsInTail());
        }

        private void PushRules(params Production[] rules)
        {
            foreach (var prod in rules)
                PushRule(prod);
        }

        public override string ToString()
        {
            string res = string.Empty;
            foreach (var head in Productions.Keys)
            {
                foreach (var rule in Productions[head])
                    res += rule.ToString() + "\n";

                // res += "\n";
            }
            return res;
        }

        public List<List<Terminal>> Develop(int count)
        {
            List<List<Terminal>> LanguageWords = new List<List<Terminal>>();

            Queue<List<Symbol>> Open = new Queue<List<Symbol>>();

            var startList = new List<Symbol>();
            startList.Add(Start);

            Open.Enqueue(startList);

            while (Open.Count > 0)
            {
                var Work = Open.Dequeue();

                if (Work.All(sym => sym is Terminal))
                {
                    LanguageWords.Add(new List<Terminal>(Work.OfType<Terminal>()));
                    count--;
                    if (count == 0)
                        break;
                }

                //for (int i = 0; i < Work.Count; ++i)
                //{
                //    Console.Write(Work[i]);
                //}
                for (int i = 0; i < Work.Count; ++i)
                {
                    if (Work[i] is Nonterminal)
                    {
                        List<Production> tails = Productions[Work[i] as Nonterminal];
                        foreach (var prod in tails)
                        {
                            var copy = new List<Symbol>(Work);
                            copy.RemoveAt(i);
                            copy.InsertRange(i, prod.Tail);
                            Open.Enqueue(copy);
                        }
                    }
                }

                //Console.WriteLine();
                //Console.ReadKey(true);
            }

            return LanguageWords;
        }
    }
}
